package com.tfswx.ruleengine.compute.gzjy;

import com.alibaba.fastjson.JSON;
import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.FelEngineImpl;
import com.greenpineyu.fel.context.FelContext;
import com.tfswx.ruleengine.compute.bean.Bds;
import com.tfswx.ruleengine.compute.bean.DataObject;
import com.tfswx.ruleengine.compute.bean.FfItem;
import com.tfswx.ruleengine.compute.bean.FormDataObject;
import com.tfswx.ruleengine.compute.bean.GzDataDTO;
import com.tfswx.ruleengine.compute.bean.RuleInfo;
import com.tfswx.ruleengine.compute.consts.Const;
import com.tfswx.ruleengine.compute.exception.AppException;
import com.tfswx.ruleengine.compute.util.CommonUtil;
import com.tfswx.ruleengine.compute.util.FileUtil;
import com.tfswx.ruleengine.compute.util.ZipUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author Ricky
 * @date 2021/3/25
 */
@Slf4j
public class RuleEngineUtil {
    private static Pattern fnRegex;
    private static List<BaseFn> fnList;
    private static String version = null;

    private RuleEngineUtil() {
    }

    public static Pattern getFnRegex() {
        if (fnRegex == null) {
            throw new AppException("请先执行：RuleEngineUtil.loadFnList()");
        }
        return fnRegex;
    }

    private static void loadFnRegex() {
        List<BaseFn> fnList = getFnList();
        StringBuilder fnListStr = new StringBuilder();
        for (BaseFn baseFn : fnList) {
            fnListStr.append("|").append(baseFn.getName());
        }
        if (fnListStr.length() > 0) {
            fnListStr.deleteCharAt(0);
            fnListStr.insert(0, "((").append(")");
            fnListStr.append("\\([^)]+\\))");
        }
        fnRegex = Pattern.compile(fnListStr.toString());
    }

    public static synchronized List<BaseFn> getFnList() {
        if (fnList == null) {
            throw new AppException("请先执行：RuleEngineUtil.loadFnList()");
        }
        return fnList;
    }

    public static void loadFnList(List<FfItem> items) {
        Map<String, byte[]> datas = new HashMap<>();
        for (FfItem item : items) {
            datas.put(item.getClassname(), item.getClassdata());
        }
        DatabaseClassLoader<BaseFn> databaseClassLoader = new DatabaseClassLoader<>(BaseFn.class, datas);

        fnList = new ArrayList<>();
        for (Map.Entry<String, byte[]> stringEntry : datas.entrySet()) {
            BaseFn instance = databaseClassLoader.getInstance(stringEntry.getKey());
            log.info("加载方法：{}", instance.getName());
            fnList.add(instance);
        }

        //
        // List<Class<BaseFn>> classesByParentFromAll = ClassUtil.getClassesByParentFromAll(BaseFn.class, "com.tfswx");
        // fnList = new ArrayList<>();
        //
        // for (Class<BaseFn> baseFnClass : classesByParentFromAll) {
        //     try {
        //         BaseFn baseFn = baseFnClass.newInstance();
        //         log.info("加载方法：{}", baseFn.getName());
        //         fnList.add(baseFn);
        //     } catch (InstantiationException e) {
        //         log.warn("构造函数异常");
        //     } catch (IllegalAccessException e) {
        //         log.warn("没有权限访问构造函数");
        //     }
        // }

        loadFnRegex();
    }

    protected static FelEngine getFelEngine() {
        FelEngine fel = new FelEngineImpl();
        List<BaseFn> fnList = getFnList();
        for (BaseFn baseFn : fnList) {
            fel.addFun(baseFn);
        }
        FelContext context = fel.getContext();
        context.set("fel", fel);
        return fel;
    }

    protected static FelEngine getFelEngine(List<Bds> bdsList, List<DataObject> dataList) {
        FelEngine fel = getFelEngine();
        FelContext context = fel.getContext();
        context.set("bdsList", bdsList);
        context.set("$parant", dataList);

        return fel;
    }

    /**
     * 过滤案卡数据
     *
     * @param data data
     * @param zdjh zdjh
     * @return java.util.List<com.tfswx.jcyw.ajznzj.dto.tyyw.FormDataObject>
     * @author Ricky
     * @date 2021/4/6
     */
    public static List<DataObject> filterData(List<FormDataObject> data, List<String> zdjh, String hcjgbh) {
        List<DataObject> retData = new ArrayList<>();
        if (data == null) {
            return retData;
        }
        DataObject akDataDTO;
        for (FormDataObject datum : data) {
            akDataDTO = new DataObject();
            String stbm = datum.getStbm();
            String id = datum.getId();
            String lbbm = datum.getLbbm();
            String dttj = datum.getDttj();
            String fid = datum.getFid();
            String sjbs = datum.getSjbs();

            akDataDTO.setStbm(stbm);
            akDataDTO.setDttj(dttj);
            akDataDTO.setFid(fid);
            akDataDTO.setId(id);
            akDataDTO.setLbbm(lbbm);
            akDataDTO.setSjbs(sjbs);
            List<FormDataObject.ZdItemDTO> zdsj = datum.getZdsj();
            List<FormDataObject.ZdItemDTO> dataEntityZdjh = getZdjh(zdsj, zdjh, stbm);
            // zdbs
            String zdbs = dataEntityZdjh.stream().filter(zdItemDTO -> "Y".equals(zdItemDTO.getSfzj())).map(item -> "&" + item.getZdmc() + "=" + item.getBgqzdbmz()).reduce((a, b) -> a + b).orElse("");
            List<DataObject.ZdItemDTO> zdItemList = dataEntityZdjh.stream().map(item -> {
                DataObject.ZdItemDTO zdItemDTO = new DataObject.ZdItemDTO();
                zdItemDTO.setHczdbh(CommonUtil.getUuid());
                zdItemDTO.setHcjgbh(hcjgbh);
                zdItemDTO.setZdbs(id);
                zdItemDTO.setStbsjbs(id);
                zdItemDTO.setZdbs("stbm=" + stbm + zdbs + "&zdmc=" + item.getZdmc());
                zdItemDTO.setStbm(stbm);
                zdItemDTO.setLbbm(lbbm);
                zdItemDTO.setDttj(dttj);
                zdItemDTO.setFid(fid);
                zdItemDTO.setSjbs(sjbs);
                zdItemDTO.setZdmc(item.getZdmc());
                zdItemDTO.setZdzwmc(item.getZdzwmc());
                zdItemDTO.setSfzj(item.getSfzj());
                zdItemDTO.setZjsccl(item.getZjsccl());
                zdItemDTO.setZdlx(item.getZdlx());
                zdItemDTO.setBgqzdbmz(item.getBgqzdbmz());
                zdItemDTO.setBgqzdwbz(item.getBgqzdwbz());
                zdItemDTO.setBghzdbmz(item.getBghzdbmz());
                zdItemDTO.setBghzdwbz(item.getBghzdwbz());
                zdItemDTO.setBgsj(item.getBgsj());
                return zdItemDTO;
            }).collect(Collectors.toList());
            akDataDTO.setZdsj(zdItemList);
            akDataDTO.setCbsj(filterData(datum.getCbsj(), zdjh, hcjgbh));
            if (!akDataDTO.getCbsj().isEmpty() || !akDataDTO.getZdsj().isEmpty()) {
                retData.add(akDataDTO);
            }
        }
        return retData;
    }

    /**
     * 获取字段集合
     *
     * @param zdsj zdsj
     * @param zdjh zdjh
     * @param stbm stbm
     * @return java.util.List<com.tfswx.jcyw.ajznzj.dto.tyyw.AkFormDataGetOutputDTO.DataFieldDTO>
     * @author Ricky
     * @date 2021/4/16
     */
    private static List<FormDataObject.ZdItemDTO> getZdjh(List<FormDataObject.ZdItemDTO> zdsj, List<String> zdjh, String stbm) {
        if (zdsj == null) {
            return new ArrayList<>();
        }
        return zdsj.stream().filter(item -> {
            if ("Y".equals(item.getSfzj())) {
                return true;
            }
            for (String s : zdjh) {
                String[] split = s.split("\\.");
                if (stbm.equals(split[0]) && item.getZdmc().equals(split[1])) {
                    return true;
                }
            }
            return false;
        }).collect(Collectors.toList());
    }

    public static List<String> getZdjh(List<RuleInfo> ruleInfoList) {
        HashSet<String> zdset = new HashSet<>();
        for (RuleInfo ruleInfo : ruleInfoList) {
            List<String> zdjh = ruleInfo.getGzdy().getZdList();
            zdset.addAll(zdjh);
        }
        return new ArrayList<>(zdset);
    }

    @Deprecated
    public static GzDataDTO getGzData(File file) {
        return getGzData(file, GzDataDTO.class);
    }

    @Deprecated
    public static <T> T getGzData(File file, Class<T> tClass) {
        try {
            File tempDirectory = new File(CommonUtil.getUuid());
            boolean mkdirs = tempDirectory.mkdirs();
            if (!mkdirs) {
                log.error("文件夹创建失败");
            }
            ZipUtil.extract(file, tempDirectory, Const.ZIP_PASSWORD);
            File[] files = tempDirectory.listFiles();
            if (files == null || files.length <= 0) {
                throw new AppException("规则包异常");
            }

            String gzInfosStr = FileUtils.readFileToString(files[0], StandardCharsets.UTF_8);

            // 删除生成的文件夹
            FileUtils.forceDelete(tempDirectory);

            return JSON.parseObject(gzInfosStr, tClass);
        } catch (IOException e) {
            throw new AppException("获取规则数据异常", e);
        }
    }


    public static GzDataDTO getGzDataFromFile(File file) {
        return getGzDataFromFile(file, GzDataDTO.class);
    }


    public static <T> T getGzDataFromFile(File file, Class<T> tClass) {
        try {
            String gzInfosStr = FileUtil.readGzbFromFile(file);
            return JSON.parseObject(gzInfosStr, tClass);
        } catch (IOException e) {
            throw new AppException("获取规则数据异常", e);
        }
    }

    @Deprecated
    @SneakyThrows
    public static File export(Object o, File outFileDir) {
        File file = new File(outFileDir, "gzInfo.json");
        FileUtils.writeStringToFile(file, JSON.toJSONString(o), StandardCharsets.UTF_8);
        String fileName = outFileDir.getName() + ".zip";
        File zipFile = new File(outFileDir.getParentFile(), fileName);

        ArrayList<File> files = new ArrayList<>();
        files.add(file);
        ZipUtil.compression(zipFile, files, Const.ZIP_PASSWORD, StandardCharsets.UTF_8);
        FileUtils.forceDelete(outFileDir);
        return zipFile;
    }

    @SneakyThrows
    public static File exportToGzb(Object o, File outFileDir, String fileName) {
        if (!outFileDir.exists()) {
            outFileDir.mkdirs();
        }
        File gzbFile = new File(outFileDir, fileName + ".gzb");
        FileUtil.writeGzbToFile(gzbFile, o);
        return gzbFile;
    }

    public static void main(String[] args) {

        // String s = "reg(" + "'.*('+join('[\"123\",\"456\"]','|')+').*'" + ", '123,789')";
        String s = "arrEq(split('1,2,3',','),split('3,1,2',','))";
        FelEngine felEngine = getFelEngine();
        Object eval = felEngine.eval(s);
        System.out.println(JSON.toJSONString(eval));
    }

    public static boolean isGoodBracket(String s) {
        Stack<Character> a = new Stack<>();

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                a.push(')');
            }
            if (c == '[') {
                a.push(']');
            }
            if (c == '{') {
                a.push('}');
            }

            if (c == ')' || c == ']' || c == '}') {
                if (a.empty()) {
                    return false;
                }
                if (a.pop() != c) {
                    return false;
                }
            }
        }

        return a.empty();
    }

    private static BaseFn indexOf(List<BaseFn> fnList, String bds, int i) {
        for (BaseFn fn : fnList) {
            String name = fn.getName();
            int startIdx = i - name.length();
            if (startIdx < 0) {
                continue;
            }
            if (bds.substring(startIdx, i).equals(name)) {
                return fn;
            }
        }
        return null;
    }

    public static List<String> getFnBdsList(String bds) {
        ArrayList<String> fnBdsList = new ArrayList<>();
        int startIdx = -1;
        int cnt = 0;
        List<BaseFn> fnList = getFnList();
        for (int i = 0; i < bds.length(); i++) {
            char c = bds.charAt(i);
            if (c == '(') {
                cnt++;
                if (startIdx == -1) {
                    BaseFn fn = indexOf(fnList, bds, i);
                    if (fn != null) {
                        startIdx = i - fn.getName().length();
                    } else {
                        cnt--;
                    }
                }
            }
            if (c == ')') {
                if (cnt > 0) {
                    cnt--;
                }
                if (cnt == 0 && startIdx != -1) {
                    fnBdsList.add(bds.substring(startIdx, i + 1));
                    startIdx = -1;
                }
            }
        }

        return fnBdsList;
    }

    public static synchronized String getGzyqVersion() {
        if (version == null) {
            version = getVersion();
        }
        return version;
    }


    private static String getVersion() {
        ClassPathResource classPathResource = new ClassPathResource("META-INF/build-info.properties");
        try (InputStream inStream = classPathResource.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream(inStream.available())) {
            byte[] buff = new byte[2048];
            int len;
            while ((len = inStream.read(buff)) > 0) {
                bos.write(buff, 0, len);
            }
            byte[] bytes = bos.toByteArray();
            bos.flush();
            Properties p = new Properties();
            p.load(new ByteArrayInputStream(bytes));
            String version = p.getProperty("build.dependencies.rule-compute");
            if (StringUtils.isBlank(version)) {
                version = p.getProperty("build.version");
            }
            return version;
        } catch (IOException e) {
            throw new AppException("读取版本号失败", e);
        }
    }
}
